﻿using System;
using System.Collections.Generic;
using System.Threading;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using O1.Kernel.IO.KeyGeneration;
using O1.Kernel.Storage.Transactions;
using Tests.O1.Stubs;

namespace Tests.O1.Kernel.IO.KeyGeneration
{
    [TestClass]
    public class SequenceCache32Tests
    {
        [TestMethod, TestCategory("Unit Test")]
        public void SequenceCache32ThrowsInvalidArgumentOutOfRangeExceptionWithNegativeCache()
        {
            Action test = () => new SequenceCache32(-1);
            test.ShouldThrow<ArgumentOutOfRangeException>();
        }

        [TestMethod, TestCategory("Unit Test")]
        public void SequenceCache32ThrowsInvalidArgumentOutOfRangeExceptionWithZeroCache()
        {
            Action test = () => new SequenceCache32(0);
            test.ShouldThrow<ArgumentOutOfRangeException>();
        }

        [TestMethod, TestCategory("Unit Test")]
        public void SequenceCache32ThrowsInvalidArgumentOutOfRangeExceptionWithNegativeCacheWithProvider()
        {
            var testSeq = new InMemorySequenceProvider32(int.MinValue);
            Action test = () => new SequenceCache32(testSeq, -1);
            test.ShouldThrow<ArgumentOutOfRangeException>();
        }

        [TestMethod, TestCategory("Unit Test")]
        public void SequenceCache32ThrowsInvalidArgumentOutOfRangeExceptionWithZeroCacheWithProvider()
        {
            var testSeq = new InMemorySequenceProvider32(int.MinValue);
            Action test = () => new SequenceCache32(testSeq, 0);
            test.ShouldThrow<ArgumentOutOfRangeException>();
        }

        [TestMethod, TestCategory("Unit Test")]
        public void SequenceCache32DoesNotFailWithCachSizeOf1()
        {
            var target = new SequenceCache32(1);

            var ids = new HashSet<int>();
            for (var i = 0; i < 300; i++)
            {
                var value = target.NextId();
                ids.Add(value).Should().BeTrue("iteration {0} resulted in a duplicate value {1}", i, value);
            }
        }

        [TestMethod, TestCategory("Unit Test")]
        public void SequenceCache32DoesNotDuplicateWithMinStart()
        {
            var target = new SequenceCache32(50);

            var ids = new HashSet<int>();
            for(var i = 0; i < 300; i++)
            {
                var value = target.NextId();
                ids.Add(value).Should().BeTrue("iteration {0} resulted in a duplicate value {1}", i, value);
            }
        }

        [TestMethod, TestCategory("Unit Test")]
        public void SequenceCache32DoesNotDuplicateWith1Start()
        {
            var testSeq = new InMemorySequenceProvider32(1);
            var target = new SequenceCache32(testSeq, 50);

            var ids = new HashSet<int>();
            for (var i = 0; i < 300; i++)
            {
                var value = target.NextId();
                ids.Add(value).Should().BeTrue("iteration {0} resulted in a duplicate value {1}", i, value);
            }
        }

        [TestMethod, TestCategory("Unit Test")]
        public void SequenceCache32DoesNotDuplicateWithCompetingThreads()
        {
            var testSeq = new InMemorySequenceProvider32(1);
            var target = new SequenceCache32(testSeq, 50);
            var ids = new HashSet<int>();

            ThreadStart action = () =>
            {
                for (var i = 0; i < 300; i++)
                {
                    var value = target.NextId();
                    ids.Add(value).Should().BeTrue("iteration {0} resulted in a duplicate value {1}", i, value);
                }
            };

            Thread[] tasks = new Thread[15];
            for (int i = 0; i < 15; i++)
            {
                tasks[i] = new Thread(action);
            }

            for (int i = 0; i < 15; i++)
            {
                tasks[i].Start();
            }

            for (int i = 0; i < 15; i++)
            {
                tasks[i].Join();
            }
        }

        [TestMethod, TestCategory("Unit Test")]
        public void SequenceCache32InvokesProviderDispose()
        {
            var testSeq = new InMemorySequenceProvider32(1, 5);
            using (var target = new SequenceCache32(testSeq, 50))
            {
                testSeq.DisposeInvoked.Should().BeFalse();
            }

            testSeq.DisposeInvoked.Should().BeTrue();
        }
    }
}
